package com.server.common.security;

import javax.crypto.Cipher;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;

/**
 * RSA工具类
 * 
 * @author JJ
 *
 */
public class RSAUtils extends Coder {

	private static final String KEY_ALGORTHM = "RSA";
	private static final String SIGNATURE_ALGORTHM = "MD5withRSA";

	private static final String PUBLIC_KEY = "RSAPublicKey"; // 公钥
	private static final String PRIVATE_KEY = "RSAPrivateKey"; // 私钥

	/**
	 * 生成秘钥对
	 * 
	 * 密钥的长度越长，安全性就越好，但是加密解密所用的时间就会越多。而一次能加密的密文长度也与密钥的长度成正比。一次能加密的密文长度为：密钥的长度/8-
	 * 11。所以1024bit长度的密钥一次可以加密的密文为1024/8-11=117bit。所以非对称加密一般都用于加密对称加密算法的密钥，
	 * 而不是直接加密内容。对于小文件可以使用RSA加密，但加密过程仍可能会使用分段加密。
	 * 
	 * @return
	 * @throws Exception
	 */
	public static HashMap<String, Object> initKey() throws Exception {
		KeyPairGenerator generator = KeyPairGenerator.getInstance(KEY_ALGORTHM);
		generator.initialize(1024); // 密钥的初始化长度为1024位
		KeyPair keyPair = generator.generateKeyPair();
		RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
		RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
		HashMap<String, Object> keyMap = new HashMap<String, Object>();
		keyMap.put(PUBLIC_KEY, publicKey);
		keyMap.put(PRIVATE_KEY, privateKey);
		return keyMap;
	}

	/**
	 * 获取公钥，并转换为String
	 * 
	 * @param keyMap
	 * @return
	 * @throws Exception
	 */
	public static String getPublicKey(HashMap<String, Object> keyMap)
			throws Exception {
		Key key = (Key) keyMap.get(PUBLIC_KEY);
		return encryptBase64(key.getEncoded());
	}

	/**
	 * 取得私钥，并转化为String类型
	 * 
	 * @param keyMap
	 * @return
	 * @throws Exception
	 */
	public static String getPrivateKey(HashMap<String, Object> keyMap)
			throws Exception {
		Key key = (Key) keyMap.get(PRIVATE_KEY);
		return encryptBase64(key.getEncoded());
	}

	/**
	 * 用私钥加密
	 * 
	 * @param data
	 *            ： 加密数据
	 * @param key
	 *            ： 秘钥
	 * @return
	 * @throws Exception
	 */
	public static byte[] encryptByPrivateKey(byte[] data, String key)
			throws Exception {
		// 解密秘钥
		byte[] keyBytes = decryptBase64(key);
		// 取私钥
		PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(
				keyBytes);
		KeyFactory factory = KeyFactory.getInstance(KEY_ALGORTHM);
		Key privateKey = factory.generatePrivate(pkcs8EncodedKeySpec);
		// 加密
		Cipher cipher = Cipher.getInstance(factory.getAlgorithm());
		cipher.init(Cipher.ENCRYPT_MODE, privateKey);
		return cipher.doFinal(data);
	}

	/**
	 * 用私钥解密
	 * 
	 * @param data
	 *            ：解密数据
	 * @param key
	 *            ： 秘钥
	 * @return
	 * @throws Exception
	 */
	public static byte[] decryptByPrivateKey(byte[] data, String key)
			throws Exception {
		// 私钥解密
		byte[] keyBytes = decryptBase64(key);
		PKCS8EncodedKeySpec pKeySpec = new PKCS8EncodedKeySpec(keyBytes);
		KeyFactory factory = KeyFactory.getInstance(KEY_ALGORTHM);
		Key privateKey = factory.generatePrivate(pKeySpec);
		// 解密
		Cipher cipher = Cipher.getInstance(factory.getAlgorithm());
		cipher.init(cipher.DECRYPT_MODE, privateKey);
		return cipher.doFinal(data);
	}

	/**
	 * 公钥加密
	 * 
	 * @param data
	 *            ： 加密数据
	 * @param key
	 *            ：秘钥
	 * @return
	 * @throws Exception
	 */
	public static byte[] encryptByPublicKey(byte[] data, String key)
			throws Exception {
		// 公钥解密
		byte[] keyBytes = decryptBase64(key);
		X509EncodedKeySpec pKeySpec = new X509EncodedKeySpec(keyBytes);
		KeyFactory factory = KeyFactory.getInstance(KEY_ALGORTHM);
		Key publicKey = factory.generatePublic(pKeySpec);
		// 加密
		Cipher cipher = Cipher.getInstance(factory.getAlgorithm());
		cipher.init(Cipher.ENCRYPT_MODE, publicKey);
		return cipher.doFinal(data);
	}

	/**
	 * 公钥解密
	 * 
	 * @param data
	 *            ： 解密数据
	 * @param key
	 *            ：秘钥
	 * @return
	 * @throws Exception
	 */
	public static byte[] decryptByPublicKey(byte[] data, String key)
			throws Exception {
		// 公钥解密
		byte[] keyBytes = decryptBase64(key);
		X509EncodedKeySpec pKeySpec = new X509EncodedKeySpec(keyBytes);
		KeyFactory factory = KeyFactory.getInstance(KEY_ALGORTHM);
		Key publicKey = factory.generatePublic(pKeySpec);
		// 解密
		Cipher cipher = Cipher.getInstance(factory.getAlgorithm());
		cipher.init(Cipher.DECRYPT_MODE, publicKey);
		return cipher.doFinal(data);
	}

	/**
	 * 私钥签名
	 * 
	 * @param data
	 * @param privateKey
	 * @return
	 * @throws Exception
	 */
	public static String sign(byte[] data, String privateKey) throws Exception {
		// 解密私钥
		byte[] keyBytes = decryptBase64(privateKey);
		PKCS8EncodedKeySpec pKeySpec = new PKCS8EncodedKeySpec(keyBytes);
		// 指定加密算法
		KeyFactory factory = KeyFactory.getInstance(KEY_ALGORTHM);
		// 获取私钥对象
		PrivateKey key = factory.generatePrivate(pKeySpec);
		// 用私钥对信息生成数字签名
		Signature signature = Signature.getInstance(SIGNATURE_ALGORTHM);
		signature.initSign(key);
		signature.update(data);
		return encryptBase64(signature.sign());
	}

	/**
	 * 校验数字签名
	 * 
	 * @param data
	 *            ： 加密数据
	 * @param publicKey
	 *            ： 公钥
	 * @param sign
	 *            ： 数字签名
	 * @return
	 * @throws Exception
	 */
	public static boolean verify(byte[] data, String publicKey, String sign)
			throws Exception {
		// 解密公钥
		byte[] keyBytes = decryptBase64(publicKey);
		X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
		// 指定加密算法
		KeyFactory factory = KeyFactory.getInstance(KEY_ALGORTHM);
		// 取公钥匙对象
		PublicKey key = factory.generatePublic(keySpec);
		Signature signature = Signature.getInstance(SIGNATURE_ALGORTHM);
		signature.initVerify(key);
		signature.update(data);
		// 验证签名是否正常
		return signature.verify(decryptBase64(sign));
	}

	/**
	 * 根据文件名获取公钥
	 * 
	 * @param fileName
	 * @return
	 * @throws Exception
	 */
	public static String getPublicKeyByFileName(String fileName)
			throws Exception {
		InputStream inputStream = RSAUtils.class.getResourceAsStream("/"
				+ fileName);
		BufferedReader reader = new BufferedReader(new InputStreamReader(
				inputStream));
		StringBuilder sb = new StringBuilder();
		String line = null;
		while ((line = reader.readLine()) != null) {
			if (line.startsWith("-")) { // 去除注释
				continue;
			}
			sb.append(line);
		}
		return sb.toString();
	}

}
